/* AbstractType.java
 *
 * Copyright 2006, Tim Dwelle.
 *
 * Licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.  See
 * the License for the specific language governing
 * permissions and limitations under the License.
 *
 */

package dowry.datatype;

import java.math.*;
import java.util.*;
import dowry.*;

/**
 * AbstractType provides an abstract baseclass for
 * implementations of datatype.
 *
 */
public abstract class AbstractType implements Datatype
{
    /**
     * Constant for the prefix used to preface entity
     * names.
     *
     */
    public static final String ENTITY_PREFIX = Messages.get("entity_");

    /**
     * Constant for the error message when validation finds
     * a null value when a value is required.
     *
     */
    public static final String ERROR_REQUIRED = Messages.get("ERROR_REQUIRED");

    /**
     * Constant for the configuration key containing the
     * datatype's <code>id</code> property indicating the
     * unique ID of the element associated with this
     * datatype.
     *
     */
    public static final String ID = "id";

    /**
     * Constant for the configuration key containing the
     * datatype's <code>javaClass</code> property indicating
     * the name of the remote Java class to which this type
     * refers.
     *
     */
    public static final String JAVA_CLASS = "javaClass";

    /**
     * Constant for the configuration key containing the
     * datatype's <code>required</code> property indicating
     * whether or not the value can be null.
     *
     */
    public static final String REQUIRED = "required";

    /**
     * Constant for the configuration key containing the
     * datatype's <code>type</code> property representing
     * the name of the datatype's specific type.  This type
     * is a string, and not necessarily equivalent to the
     * object's class..
     *
     */
    public static final String TYPE = "type";

    /**
     * Constant for the configuration key containing the
     * datatype's <code>value</code> property indicating
     * the raw value to be validated.
     *
     */
    public static final String VALUE = "value";

    /**
     * Gets the name of the Javascript class that serves
     * as the client side equivalent to this Java Datatype.
     *
     * <p>
     * The default implementation will return the
     * fully-qualified Java class name.
     * </p>
     *
     * @return  the string Javascript class name of the
     *          datatype
     *
     */
    public abstract String getJsClass();

    /**
     * Tests if this datatype is a handler for the
     * specified Dowry type name.
     *
     * @param  typeName  the Dowry type name
     *
     * @return           true if this datatype can handle
     *                   the specified type name; false
     *                   otherwise
     *
     */
    public abstract boolean isType(String typeName);

    /**
     * Converts the Java class for this datatype to the type
     * representation used by Dowry on the client.
     *
     * @param  c   the class to get the type name for
     *
     * @return     the string representation of type to be
     *             used in Dowry
     *
     */
    public abstract String toType(Class c);

    /**
     * Validates a value representation according to the
     * validation rules for the datatype.
     *
     * <p>
     * This base implementation of valide simply validates
     * that the provided value is not null, if the datatype
     * is required (based on the boolean map key <code>
     * required</code>).
     * </p>
     *
     * @param  cfg  a map containing all the configuration
     *              information relevant to the local
     *              representation of this datatype
     *
     * @return      the string validation error message,
     *              if the validation fails; null otherwise
     *
     */
    public String validate(Map cfg)
    {
        String msg = null;

        Object val = cfg.get(VALUE);
        Boolean b = getBoolean(cfg, REQUIRED);

        if (val == null &&
            b != null &&
            b.equals(Boolean.TRUE))
        {
            msg = ERROR_REQUIRED;
        }

        return msg;
    }

    /**
     * Utility for getting a big decimal value out of the
     * map.
     *
     * @param  cfg  the validation map
     *
     * @param  key  the key to retrieve
     *
     * @return      the associated value, in the requested
     *              datatype, or null if the value cannot
     *              be found or is not of the proper type
     *
     */
    protected BigDecimal getBigDecimal(Map cfg, String key)
    {
        BigDecimal n = null;

        try
        {
            Object val = cfg.get(key);
            if (val != null)
            {
                if (val instanceof BigDecimal)
                {
                    n = (BigDecimal) val;
                }
                else if (val instanceof String)
                {
                    String s = (String) val;
                    n = new BigDecimal(s);
                }
            }
        }
        catch(Exception ex)
        {
        }

        return n;
    }

    /**
     * Utility for getting a boolean value out of the map.
     *
     * @param  cfg  the validation map
     *
     * @param  key  the key to retrieve
     *
     * @return      the associated value, in the requested
     *              datatype, or null if the value cannot
     *              be found or is not of the proper type
     *
     */
    protected Boolean getBoolean(Map cfg, String key)
    {
        Boolean b = null;

        try
        {
            Object val = cfg.get(key);
            if (val != null)
            {
                if (val instanceof Boolean)
                {
                    b = (Boolean) val;
                }
                else if (val instanceof String)
                {
                    String s = (String) val;
                    b = Boolean.valueOf(s);
                }
            }
        }
        catch(Exception ex)
        {
        }

        return b;
    }

    /**
     * Utility for getting an integer value out of the map.
     *
     * @param  cfg  the validation map
     *
     * @param  key  the key to retrieve
     *
     * @return      the associated value, in the requested
     *              datatype, or null if the value cannot
     *              be found or is not of the proper type
     *
     */
    protected Integer getInteger(Map cfg, String key)
    {
        Integer i = null;

        try
        {
            Object val = cfg.get(key);
            if (val != null)
            {
                if (val instanceof Integer)
                {
                    i = (Integer) val;
                }
                else if (val instanceof String)
                {
                    String s = (String) val;
                    i = Integer.valueOf(s);
                }
            }
        }
        catch(Exception ex)
        {
        }

        return i;
    }

    // TODO: add more utility methods?

    /**
     * Utility for getting a string value out of the map.
     *
     * @param  cfg  the validation map
     *
     * @param  key  the key to retrieve
     *
     * @return      the associated value, in the requested
     *              datatype, or null if the value cannot
     *              be found or is not of the proper type
     *
     */
    protected String getString(Map cfg, String key)
    {
        String s = null;

        Object val = cfg.get(key);
        if (val != null)
        {
            if (val instanceof String)
            {
                s = (String) val;
            }
        }

        return s;
    }

    /**
     * Returns the class name with the package stripped off.
     *
     * @param className  string representation of the Java
     *                   class name
     *
     * @return           the unqualified class name
     *
     */
    protected String unqualify(String className)
    {
        if (className != null)
        {
            int dot = className.lastIndexOf(".");
            if (dot > -1 && dot < (className.length()-1))
            {
                className = className.substring(dot+1);
            }
        }

        return className;
    }
}